#include <chrono>
#include <ctime>
#include <iostream>
#include <string>
#include <thread>
#include <vector>

using namespace std;

#include <arpa/inet.h>
#include <atomic>
#include <netinet/in.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include "group.hpp"
#include "public.hpp"
#include "user.hpp"
#include "PCHAT.pb.h"

// 记录当前客户端登录的用户信息(例如QQ登录成功的用户)
User g_currentUser;
// 记录当前登录用户的好友列表信息(登录成功后服务器会发过来)
//这是全局变量，假如登录登出又登录，那么会写入两次，相当于好友列表重复了两遍，所以每次在写入前要clean()一下
vector<User> g_currentUserFriendList;
// 记录当前登录用户的群组列表信息(登录成功后服务器会发过来)
vector<Group> g_currentUserGroupList;

// 控制主菜单页面程序
bool isMainMenuRunning = false;

// 用于读写线程之间的通信(信号量)
sem_t rwsem;
// 记录登录状态(原子变量,这种单变量的修改不需要用到互斥锁这种重量级锁,用原子变量即可)
atomic_bool g_isLoginSuccess{false};

// 接收消息线程
void readTaskHandler(int clientfd);
// 获取系统时间（每条聊天信息需要添加时间信息）
string getCurrentTime();

// 主聊天页面程序
void mainMenu(int);
// 显示当前登录成功用户的基本信息
void showCurrentUserData();

// 聊天客户端程序实现，main线程专门用作发送线程，子线程专门用作接收线程
int main(int argc, char **argv) {
  if (argc < 3) { //用户需要输入服务端的ip和port
    cerr << "command invalid! example: ./ChatClient 127.0.0.1 6000" << endl;
    exit(-1);
  }

  // 解析通过命令行参数传递的ip和port
  char *ip = argv[1];
  uint16_t port = atoi(argv[2]);

  // 创建client端的socket
  int clientfd = socket(AF_INET, SOCK_STREAM, 0);
  if (-1 == clientfd) {
    cerr << "socket create error" << endl;
    exit(-1);
  }

  // 填写client需要连接的server信息ip+port
  sockaddr_in server;
  memset(&server, 0, sizeof(sockaddr_in));

  server.sin_family = AF_INET;
  server.sin_port = htons(port);
  server.sin_addr.s_addr = inet_addr(ip);

  // client和server进行连接
  if (-1 == connect(clientfd, (sockaddr *)&server, sizeof(sockaddr_in))) {
    cerr << "connect server error" << endl;
    close(clientfd);
    exit(-1);
  }

  // 初始化读写线程通信用的信号量(第二个参数表示是做线程间通信还是进程间通信，第三个参数表示信号量初始计数为0)
  sem_init(&rwsem, 0, 0);

  // 连接服务器成功(只是连接成功，用户还没有登录)，启动接收子线程负责接收数据,该线程只启动一次（你过来一个用户，这个用户登出了(登出后，程序不是完全退出，而是进入到显示首页面菜单的内容），再来一个用户时，这个线程不需要再启动一次）
  std::thread readTask(readTaskHandler, clientfd); // 在Linux上运行时底层相当于调用pthread_create
  readTask.detach();                               // pthread_detach

  // main线程用于接收用户输入，负责发送数据
  for (;;) {
    // 显示首页面菜单 登录、注册、退出
    cout << "========================" << endl;
    cout << "1. login" << endl;
    cout << "2. register" << endl;
    cout << "3. quit" << endl;
    cout << "========================" << endl;
    cout << "choice:";
    int choice = 0;
    cin >> choice;
    cin.get(); // 读掉缓冲区残留的回车

    switch (choice) {
    case 1: // login业务
    {
      int id = 0;
      char pwd[50] = {0};
      cout << "userid:";
      cin >> id;
      cin.get(); // 读掉缓冲区残留的回车
      cout << "userpassword:";
      cin.getline(pwd, 50);

      ChatMessage chatMessage;
      chatMessage.set_msg_type(MessageType::LOGIN_MSG);
      LoginRequest* loginRequest = chatMessage.mutable_login_req();
      loginRequest->set_user_id(id);
      loginRequest->set_password(pwd);
      string request = chatMessage.SerializeAsString(); // 将json对象序列化为字符串，发送出去

      g_isLoginSuccess = false;

      //主线程只负责发送，但是send是不阻塞的，所以需要等待子线程处理完服务器发过来的响应消息(登录成功还是登录失败？doLoginResponse中会处理成功还是失败的响应(doLoginResponse函数在readTaskHandler中被调用)，如果成功，则g_isLoginSuccess被设置为true)后，再继续发送下一条消息
      int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0);
      if (len == -1) {
        cerr << "send login msg error:" << request << endl;
        break;
      }

      sem_wait(&rwsem); // 等待信号量，由子线程处理完登录的响应消息后，通知这里

      if (g_isLoginSuccess) { //登录成功
        // 进入聊天主菜单页面
        isMainMenuRunning = true;
        mainMenu(clientfd);
      }
    } break;
    case 2: // register业务
    {
      char name[50] = {0};
      char pwd[50] = {0};
      cout << "username:";
      cin.getline(name, 50);  //cin >> name;这样的输入方式不能输入空格，例如"zhang san"只能读取到zhang
      cout << "userpassword:";
      cin.getline(pwd, 50);

      ChatMessage chatMessage;
      chatMessage.set_msg_type(MessageType::REG_MSG);
      RegisterRequest *registerRequest = chatMessage.mutable_register_req();
      registerRequest->set_user_name(name);
      registerRequest->set_password(pwd);
      string request = chatMessage.SerializeAsString();

      int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0);
      if (len == -1) {
        cerr << "send reg msg error:" << request << endl;
        break;
      }

      sem_wait(&rwsem); // 等待信号量，子线程处理完注册消息会通知
    } break;
    case 3: // quit业务
      close(clientfd);
      sem_destroy(&rwsem);
      exit(EXIT_SUCCESS);
    default:
      cerr << "invalid input!" << endl;
      break;
    }
  }

  return 0;
}

// 处理注册操作的响应逻辑
void doRegResponse(ChatMessage& response) {
  if (0 != response.register_resp().errnum()) // 注册失败
  {
    cerr << "name is already exist, register error!" << endl;
  } else // 注册成功
  {
    cout << "name register success, userid is " << response.register_resp().user_id() << ", do not forget it!" << endl;
  }
}

// 处理登录操作的响应逻辑
void doLoginResponse(ChatMessage &response) {
  const LoginResponse& loginResponse =  response.login_resp();
  if (0 != loginResponse.errnum()) // 登录失败
  {
    cerr << loginResponse.errmsg() << endl;
    g_isLoginSuccess = false;
  } else // 登录成功
  {
    // 记录当前用户的id和name
    g_currentUser.setId(loginResponse.user_info().user_id());
    g_currentUser.setName(loginResponse.user_info().user_name());

    // 记录当前用户的好友列表信息
    if (loginResponse.friends_size() != 0) {
      g_currentUserFriendList.clear(); // 初始化

      for (const P_User& p_user : loginResponse.friends()) {
        User user;
        user.setId(p_user.user_id());
        user.setName(p_user.user_name());
        user.setState(p_user.state());
        g_currentUserFriendList.push_back(user);
      }
    }

    // 记录当前用户的群组列表信息
    if (loginResponse.groups_size() != 0) {
      g_currentUserGroupList.clear(); // 初始化(清空之前登录的用户保存的群组信息)

      for (const P_Group &p_group : loginResponse.groups()) {
        Group group;
        group.setId(p_group.group_id());
        group.setName(p_group.group_name());
        group.setDesc(p_group.description());

        for (const P_GroupMember &groupMember : p_group.members()) {
          GroupUser user;
          user.setId(groupMember.user().user_id());
          user.setName(groupMember.user().user_name());
          user.setState(groupMember.user().state());
          user.setRole(groupMember.role());
          group.getUsers().push_back(user);
        }

        g_currentUserGroupList.push_back(group);
      }
    }

    // 显示登录用户的基本信息
    showCurrentUserData();

    // 显示当前用户的离线消息  个人聊天信息或者群组消息
    if (loginResponse.offline_msgs_size() != 0) {
      for (const OfflineMsg& offlineMsg : loginResponse.offline_msgs()) {
        // time + [id] + name + " said: " + xxx
        if (MsgChatType::PRIVATE_CHAT ==  offlineMsg.msgid()) {
          const ChatContent& chatContent = offlineMsg.one_chat_msg().chat_content();
          cout << chatContent.timestamp() << " [" << chatContent.from_user_id() << "]"
               << chatContent.from_name() << " said: " << chatContent.content() << endl;
        } else {
          ChatContent chatContent = offlineMsg.group_chat_msg().chat_content();
          cout << "群消息[" << offlineMsg.group_chat_msg().group_id()
               << "]:" << chatContent.timestamp() << " ["
               << chatContent.from_user_id() << "]"
               << " [" << chatContent.from_name() << "]"
               << " said: " << chatContent.content() << endl;
        }
      }
    }

    g_isLoginSuccess = true;
  }
}

// 子线程 - 接收线程
void readTaskHandler(int clientfd) {
  for (;;) {
    char buffer[BUFSIZE] = {0};
    int len = recv(clientfd, buffer, BUFSIZE, 0); // 阻塞了
    if (-1 == len || 0 == len) {
      close(clientfd);
      exit(-1);
    }

    // 接收ChatServer转发的数据，反序列化生成json数据对象
    ChatMessage chatMessage;
    chatMessage.ParseFromString(buffer);
    int msgtype = chatMessage.msg_type();
    if (ONE_CHAT_MSG == msgtype) { 
      const ChatContent& chatContent = chatMessage.one_chat_msg().chat_content();
      cout << chatContent.timestamp() << " [" << chatContent.from_user_id() << "]"
           << chatContent.from_name() << " said: " << chatContent.content()
           << endl;
      continue;
    } 

    if (GROUP_CHAT_MSG == msgtype) {
      const ChatContent& chatContent = chatMessage.group_chat_msg().chat_content();
      cout << "群消息[" << chatMessage.group_chat_msg().group_id() << "]:" << chatContent.timestamp()
           << " [" << chatContent.from_user_id() << "]" << chatContent.from_name()
           << " said: " << chatContent.content() << endl;
      continue;
    }

    if (LOGIN_MSG_ACK == msgtype) {
      doLoginResponse(chatMessage); // 处理登录响应的业务逻辑
      sem_post(&rwsem);    // 通知主线程，登录结果处理完成
      continue;
    }

    if (REG_MSG_ACK == msgtype) {
      doRegResponse(chatMessage);
      sem_post(&rwsem); // 通知主线程，注册结果处理完成
      continue;
    }
  }
}

// 显示当前登录成功用户的基本信息
void showCurrentUserData() {
  cout << "======================login user======================" << endl;
  cout << "current login user => id:" << g_currentUser.getId()
       << " name:" << g_currentUser.getName() << endl;
  cout << "----------------------friend list---------------------" << endl;
  //打印当前用户的好友信息列表(好友id，好友名字，好友登录状态)
  if (!g_currentUserFriendList.empty()) {
    for (User &user : g_currentUserFriendList) {
      cout << user.getId() << " " << user.getName() << " " << user.getState()
           << endl;
    }
  }
  cout << "----------------------group list----------------------" << endl;
  //打印当前用户的群组信息列表(群号，群名，群描述)以及群组中成员的信息
  if (!g_currentUserGroupList.empty()) {
    for (Group &group : g_currentUserGroupList) {
      cout << group.getId() << "群： " << group.getName() << " " << group.getDesc()
           << endl;
      for (GroupUser &user : group.getUsers()) {
        cout << "群成员：" << user.getId() << " " << user.getName() << " " << user.getState()
             << " " << user.getRole() << endl;
      }
      cout << "+++++++++++++++++++++" << endl;
    }
  }
  cout << "======================================================" << endl;
}

// "help" command handler
void help(int fd = 0, string str = "");
// "chat" command handler
void chat(int, string);
// "addfriend" command handler
void addfriend(int, string);
// "creategroup" command handler
void creategroup(int, string);
// "addgroup" command handler
void addgroup(int, string);
// "groupchat" command handler
void groupchat(int, string);
// "loginout" command handler
void loginout(int, string);

// 系统支持的客户端命令列表
unordered_map<string, string> commandMap = {
    {"help", "显示所有支持的命令，格式: help"},
    {"chat", "一对一聊天，格式: chat:<friendid>:<message>"},
    {"addfriend", "添加好友，格式: addfriend:<friendid>"},
    {"creategroup", "创建群组，格式: creategroup:<groupname>:<groupdesc>"},
    {"addgroup", "加入群组，格式: addgroup:<groupid>"},
    {"groupchat", "群聊，格式: groupchat:<groupid>:<message>"},
    {"loginout", "注销，格式: loginout"}};

// 注册系统支持的客户端命令处理
unordered_map<string, function<void(int, string)>> commandHandlerMap = {
    {"help", help},           {"chat", chat},
    {"addfriend", addfriend}, {"creategroup", creategroup},
    {"addgroup", addgroup},   {"groupchat", groupchat},
    {"loginout", loginout}};

// 主聊天页面程序
void mainMenu(int clientfd) {
  help();

  char buffer[BUFSIZE] = {0};
  while (isMainMenuRunning) {
    cin.getline(buffer, BUFSIZE);
    string commandbuf(buffer);
    string command; // 存储命令
    int idx = commandbuf.find(":");
    if (-1 == idx) {  //loginout和help这两个命令是找不到冒号的
      command = commandbuf;
    } else {
      command = commandbuf.substr(0, idx); // 截取命令
    }
    auto it = commandHandlerMap.find(command);
    if (it == commandHandlerMap.end()) {
      cerr << "invalid input command!" << endl;
      continue;
    }

    // 调用相应命令的事件处理回调，mainMenu对修改封闭，添加新功能不需要修改该函数
    it->second(clientfd, commandbuf.substr(idx + 1, commandbuf.size() - idx)); // 调用命令处理方法
  }
}

// "help" command handler
void help(int, string) {
  cout << "show command list >>> " << endl;
  for (auto &p : commandMap) {
    cout << p.first << " : " << p.second << endl;
  }
  cout << endl;
}
// "addfriend" command handler
void addfriend(int clientfd, string str) {
  int friendid = atoi(str.c_str());

  ChatMessage chatMessage;
  chatMessage.set_msg_type(MessageType::ADD_FRIEND_MSG);
  AddFriendRequest* addFriendReq = chatMessage.mutable_add_friend_req();
  addFriendReq->set_user_id(g_currentUser.getId());
  addFriendReq->set_friend_id(friendid);
  string buffer = chatMessage.SerializeAsString();

  int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
  if (-1 == len) {
    cerr << "send addfriend msg error -> " << buffer << endl;
  }
}

// "chat" command handler
void chat(int clientfd, string str) {
  int idx = str.find(":"); // friendid:message
  if (-1 == idx) {
    cerr << "chat command invalid!" << endl;
    return;
  }

  int friendid = atoi(str.substr(0, idx).c_str());
  string message = str.substr(idx + 1, str.size() - idx);

  ChatMessage chatMessage;
  chatMessage.set_msg_type(MessageType::ONE_CHAT_MSG);
  OneChatMessage* oneChatMessage = chatMessage.mutable_one_chat_msg();
  oneChatMessage->set_to_user_id(friendid);
  ChatContent* chatContent = oneChatMessage->mutable_chat_content();
  chatContent->set_from_user_id(g_currentUser.getId());
  chatContent->set_from_name(g_currentUser.getName());
  chatContent->set_content(message);
  chatContent->set_timestamp(getCurrentTime());

  string buffer = chatMessage.SerializeAsString();

  int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
  if (-1 == len) {
    cerr << "send chat msg error -> " << buffer << endl;
  }
}

// "creategroup" command handler  "groupname:groupdesc"
void creategroup(int clientfd, string str) {
  int idx = str.find(":");
  if (-1 == idx) {
    cerr << "creategroup command invalid!" << endl;
    return;
  }

  string groupname = str.substr(0, idx);
  string groupdesc = str.substr(idx + 1, str.size() - idx);

  ChatMessage chatMessage;
  chatMessage.set_msg_type(MessageType::CREATE_GROUP_MSG);
  CreateGroupRequest* createGroupRequest = chatMessage.mutable_create_group_req();
  createGroupRequest->set_creator_id(g_currentUser.getId()); // 群的创建人
  createGroupRequest->set_group_name(groupname);
  createGroupRequest->set_description(groupdesc);

  string buffer = createGroupRequest->SerializeAsString();

  int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
  if (-1 == len) {
    cerr << "send creategroup msg error -> " << buffer << endl;
  }
}

// "addgroup" command handler
void addgroup(int clientfd, string str) {
  int groupid = atoi(str.c_str());
  ChatMessage chatMessage;
  chatMessage.set_msg_type(MessageType::ADD_GROUP_MSG);
  AddGroupRequest* addGroupReq = chatMessage.mutable_add_group_req();
  addGroupReq->set_user_id(g_currentUser.getId());
  addGroupReq->set_group_id(groupid);
  string buffer = addGroupReq->SerializeAsString();

  int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
  if (-1 == len) {
    cerr << "send addgroup msg error -> " << buffer << endl;
  }
}
// "groupchat" command handler   "groupid:message"
void groupchat(int clientfd, string str) {
  int idx = str.find(":");
  if (-1 == idx) {
    cerr << "groupchat command invalid!" << endl;
    return;
  }

  int groupid = atoi(str.substr(0, idx).c_str());
  string message = str.substr(idx + 1, str.size() - idx);

  ChatMessage chatMessage;
  chatMessage.set_msg_type(MessageType::GROUP_CHAT_MSG);
  GroupChatMessage* groupChatMsg = chatMessage.mutable_group_chat_msg();
  groupChatMsg->set_group_id(groupid);

  ChatContent* chatContent = groupChatMsg->mutable_chat_content();
  chatContent->set_from_user_id(g_currentUser.getId());
  chatContent->set_from_name(g_currentUser.getName());
  chatContent->set_content(message);
  chatContent->set_timestamp(getCurrentTime());
  string buffer = chatMessage.SerializeAsString();

  int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
  if (-1 == len) {
    cerr << "send groupchat msg error -> " << buffer << endl;
  }
}
// "loginout" command handler
void loginout(int clientfd, string) {
  ChatMessage chatMessage;
  chatMessage.set_msg_type(MessageType::LOGINOUT_MSG);
  chatMessage.mutable_logout_req()->set_user_id(g_currentUser.getId());
  string buffer = chatMessage.SerializeAsString();

  int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
  if (-1 == len) cerr << "send loginout msg error -> " << buffer << endl;
  else isMainMenuRunning = false; // mainMenu()就不再进行while循环了，退出主菜单
}

// 获取系统时间（聊天信息需要添加时间信息）
string getCurrentTime() {
  auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
  struct tm *ptm = localtime(&tt);
  char date[60] = {0};
  sprintf(date, "%d-%02d-%02d %02d:%02d:%02d", (int)ptm->tm_year + 1900,
          (int)ptm->tm_mon + 1, (int)ptm->tm_mday, (int)ptm->tm_hour,
          (int)ptm->tm_min, (int)ptm->tm_sec);
  return std::string(date);
}